/**
 * Class to generate an image based the diagram interchange information in a
 * BPMN 2.0 process.
 *
 * @author (Javascript) Dmitry Farafonov
 */

var ProcessDiagramGenerator = {
  options: {},

  processDiagramCanvas: [],

  activityDrawInstructions: {},

  processDiagrams: {},

  diagramBreadCrumbs: null,

  init: function () {
    // start event
    this.activityDrawInstructions["startEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawNoneStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // start timer event
    this.activityDrawInstructions["startTimerEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawTimerStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
    };

    // start event
    this.activityDrawInstructions["messageStartEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawMessageStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
    };

    // start signal event
    this.activityDrawInstructions["startSignalEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawSignalStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
    };

    // start multiple event
    this.activityDrawInstructions["startMultipleEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawMultipleStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
    };

    // signal catch
    this.activityDrawInstructions["intermediateSignalCatch"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // message catch
    this.activityDrawInstructions["intermediateMessageCatch"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingMessageEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // multiple catch
    this.activityDrawInstructions["intermediateMultipleCatch"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingMultipleEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };


    // signal throw
    this.activityDrawInstructions["intermediateSignalThrow"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // message throw
    this.activityDrawInstructions["intermediateMessageThrow"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingMessageEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // multiple throw
    this.activityDrawInstructions["intermediateMultipleThrow"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingMultipleEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // none throw
    this.activityDrawInstructions["intermediateThrowEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingNoneEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // end event
    this.activityDrawInstructions["endEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawNoneEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // error end event
    this.activityDrawInstructions["errorEndEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawErrorEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // message end event
    this.activityDrawInstructions["messageEndEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawMessageEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // signal end event
    this.activityDrawInstructions["signalEndEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawSignalEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // multiple end event
    this.activityDrawInstructions["multipleEndEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawMultipleEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // terminate end event
    this.activityDrawInstructions["terminateEndEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawTerminateEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // error start event
    this.activityDrawInstructions["errorStartEvent"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawErrorStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), activityImpl.getProperty("name"));

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // task
    this.activityDrawInstructions["task"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      // TODO:
      //console.error("task is not implemented yet");
      /*
      var activityImpl = this;
      processDiagramCanvas.drawTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), thickBorder);
      */
    };


    // user task
    this.activityDrawInstructions["userTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawUserTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // script task
    this.activityDrawInstructions["scriptTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawScriptTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // service task
    this.activityDrawInstructions["serviceTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawServiceTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // receive task
    this.activityDrawInstructions["receiveTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawReceiveTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // send task
    this.activityDrawInstructions["sendTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawSendTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // manual task
    this.activityDrawInstructions["manualTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawManualTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // businessRuleTask task
    this.activityDrawInstructions["businessRuleTask"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawBusinessRuleTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // exclusive gateway
    this.activityDrawInstructions["exclusiveGateway"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawExclusiveGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // inclusive gateway
    this.activityDrawInstructions["inclusiveGateway"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawInclusiveGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // parallel gateway
    this.activityDrawInstructions["parallelGateway"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawParallelGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // eventBasedGateway
    this.activityDrawInstructions["eventBasedGateway"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawEventBasedGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // Boundary timer
    this.activityDrawInstructions["boundaryTimer"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingTimerEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // Boundary catch error
    this.activityDrawInstructions["boundaryError"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingErrorEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // Boundary signal event
    this.activityDrawInstructions["boundarySignal"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingSignalEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // Boundary message event
    this.activityDrawInstructions["boundaryMessage"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty("isInterrupting");
      processDiagramCanvas.drawCatchingMessageEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, null);

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label)
        processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // timer catch event
    this.activityDrawInstructions["intermediateTimer"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = null;
      processDiagramCanvas.drawCatchingTimerEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), isInterrupting, activityImpl.getProperty("name"));
    };

    // subprocess
    this.activityDrawInstructions["subProcess"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      // TODO:

      processDiagramCanvas.setConextObject(activityImpl);

      var isExpanded = activityImpl.getProperty("isExpanded");
      var isTriggeredByEvent = activityImpl.getProperty("triggeredByEvent");
      if (isTriggeredByEvent == undefined) {
        isTriggeredByEvent = true;
      }
      // TODO: check why isTriggeredByEvent = true when undefined
      isTriggeredByEvent = false;

      if (isExpanded != undefined && isExpanded == false) {
        processDiagramCanvas.drawCollapsedSubProcess(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
          activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
      } else {
        processDiagramCanvas.drawExpandedSubProcess(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(),
          activityImpl.getWidth(), activityImpl.getHeight(), isTriggeredByEvent);
      }

      //console.error("subProcess is not implemented yet");
    };

    // call activity
    this.activityDrawInstructions["callActivity"] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);
      processDiagramCanvas.drawCollapsedCallActivity(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    $(document).ready(function () {
      // Protect right click on SVG elements (and on canvas too)
      document.body.oncontextmenu = function (event) {
        if (window.event.srcElement.tagName == "shape" || window.event.srcElement.tagName == "DIV" && window.event.srcElement.parentElement.className == "diagram") {

          // IE DIAGRAM CANVAS OR SHAPE DETECTED!
          return false;
        }
        return (!Object.isSVGElement(window.event.srcElement));
      };
    });
  },

  getActivitiLabel: function (activityImpl) {
    /*
      TODO: Label object should be in activityImpl and looks like:
      {
        x: 250,
        y: 250,
        width: 80,
        height: 30
      }
      And then:
      if (!activityImpl.label)
        return null;
      var label = activityImpl.label;
      label.text = activityImpl.name;
      return label;
    */

    // But now default label for all events is:
    return {
      text: activityImpl.getProperty("name"),
      x: activityImpl.getX() + .5 + activityImpl.getWidth() / 2,
      y: activityImpl.getY() + .5 + activityImpl.getHeight() + ICON_PADDING,
      width: 100,
      height: 0
    };
  },

  generateDiagram: function (processDefinitionDiagramLayout) {
    // Init canvas
    var processDefinitionId = processDefinitionDiagramLayout.processDefinition.id;
    //console.log("Init canvas ", processDefinitionId);

    if (this.getProcessDiagram(processDefinitionId) != undefined) {
      // TODO: may be reset canvas if exists.. Or just show
      //console.log("ProcessDiagram '" + processDefinitionId + "' is already generated. Just show it.");
      return;
    }
    var processDiagram = this.initProcessDiagramCanvas(processDefinitionDiagramLayout);
    var processDiagramCanvas = processDiagram.diagramCanvas;

    // Draw pool shape, if process is participant in collaboration

    if (processDefinitionDiagramLayout.participantProcess != undefined) {
      //console.log("Draw pool shape");
      var pProc = processDefinitionDiagramLayout.participantProcess;
      processDiagramCanvas.drawPoolOrLane(pProc.x, pProc.y, pProc.width, pProc.height, pProc.name);
    }

    var laneSets = processDefinitionDiagramLayout.laneSets;
    var activities = processDefinitionDiagramLayout.activities;
    var sequenceFlows = processDefinitionDiagramLayout.sequenceFlows;


    pb1.set('value', 0);
    var cnt = 0;
    if (laneSets)
      for (var i in laneSets) {
        cnt += laneSets[i].lanes.length;
      }
    if (activities)
      cnt += activities.length;
    if (sequenceFlows)
      cnt += sequenceFlows.length;
    var step = (cnt > 0) ? 100 / cnt : 0;
    var progress = 0;
    //console.log("progress bar step: ", step);

    var task1 = new $.AsyncQueue();

    // Draw lanes

    task1.add(function (task1) {
      if (!laneSets) laneSets = [];
      //console.log("> draw lane sets, count:", laneSets.length)
    });

    for (var i in laneSets) {
      var laneSet = laneSets[i];
      //laneSet.id, laneSet.name

      task1.add(laneSet.lanes, function (task1, lane) {
        progress += step;
        pb1.set('value', parseInt(progress));

        //console.log("--> laneId: " + lane.name + ", name: " + lane.name);

        processDiagramCanvas.drawPoolOrLane(lane.x, lane.y, lane.width, lane.height, lane.name);
      });
    }

    // Draw activities

    task1.add(function (task1) {
      if (!activities) activities = [];
      //console.log("> draw activities, count:", activities.length)
    });

    var activitiesLength = activities.length;
    task1.add(activities, function (task1, activityJson) {
      var activity = new ActivityImpl(activityJson);
      activitiesLength--;
      progress += step;
      pb1.set('value', parseInt(progress));
      //console.log(activitiesLength, "--> activityId: " + activity.getId() + ", name: " + activity.getProperty("name"));
      ProcessDiagramGenerator.drawActivity(processDiagramCanvas, activity);
    });

    // Draw sequence-flows

    task1.add(function (task1) {
      if (!sequenceFlows) sequenceFlows = [];
      //console.log("> draw sequence flows, count:", sequenceFlows.length)
    });

    var flowsLength = sequenceFlows.length;
    task1.add(sequenceFlows, function (task1, flow) {
      var waypoints = [];
      for (var j in flow.xPointArray) {
        waypoints[j] = {x: flow.xPointArray[j], y: flow.yPointArray[j]};
      }
      var isDefault = flow.isDefault;
      var isConditional = flow.isConditional;
      var isHighLighted = flow.isHighLighted;

      // TODO: add source and destination for sequence flows in REST
      // parse for test
      var f = flow.flow;
      var matches = f.match(/\((.*)\)--.*-->\((.*)\)/);
      var sourceActivityId, destinationActivityId;
      if (matches != null) {
        sourceActivityId = matches[1];
        destinationActivityId = matches[2];
      }
      flow.sourceActivityId = sourceActivityId;
      flow.destinationActivityId = destinationActivityId;
      //
      flowsLength--;
      progress += step;
      pb1.set('value', parseInt(progress));

      //console.log(flowsLength, "--> flow: " + flow.flow);

      processDiagramCanvas.setConextObject(flow);
      processDiagramCanvas.drawSequenceflow(waypoints, isConditional, isDefault, isHighLighted);
    });

    task1.onComplete(function () {
      if (progress < 100)
        pb1.set('value', 100);
      //console.log("COMPLETE!!!");

      //console.timeEnd('generateDiagram');
    });

    task1.run();
  },

  getProcessDiagram: function (processDefinitionId) {
    return this.processDiagrams[processDefinitionId];
  },

  initProcessDiagramCanvas: function (processDefinitionDiagramLayout) {
    var minX = 0;
    var maxX = 0;
    var minY = 0;
    var maxY = 0;

    if (processDefinitionDiagramLayout.participantProcess != undefined) {
      var pProc = processDefinitionDiagramLayout.participantProcess;

      minX = pProc.x;
      maxX = pProc.x + pProc.width;
      minY = pProc.y;
      maxY = pProc.y + pProc.height;
    }

    var activities = processDefinitionDiagramLayout.activities;
    for (var i in activities) {
      var activityJson = activities[i];
      var activity = new ActivityImpl(activityJson);

      // width
      if (activity.getX() + activity.getWidth() > maxX) {
        maxX = activity.getX() + activity.getWidth();
      }
      if (activity.getX() < minX) {
        minX = activity.getX();
      }
      // height
      if (activity.getY() + activity.getHeight() > maxY) {
        maxY = activity.getY() + activity.getHeight();
      }
      if (activity.getY() < minY) {
        minY = activity.getY();
      }
    }

    var sequenceFlows = processDefinitionDiagramLayout.sequenceFlows;
    for (var i in sequenceFlows) {
      var flow = sequenceFlows[i];
      var waypoints = [];
      for (var j in flow.xPointArray) {
        waypoints[j] = {x: flow.xPointArray[j], y: flow.yPointArray[j]};

        // width
        if (waypoints[j].x > maxX) {
          maxX = waypoints[j].x;
        }
        if (waypoints[j].x < minX) {
          minX = waypoints[j].x;
        }
        // height
        if (waypoints[j].y > maxY) {
          maxY = waypoints[j].y;
        }
        if (waypoints[j].y < minY) {
          minY = waypoints[j].y;
        }
      }
    }

    var laneSets = processDefinitionDiagramLayout.laneSets;
    for (var i in laneSets) {
      var laneSet = laneSets[i];
      //laneSet.id, laneSet.name

      for (var j in laneSet.lanes) {
        var lane = laneSet.lanes[j];
        // width
        if (lane.x + lane.width > maxX) {
          maxX = lane.x + lane.width;
        }
        if (lane.x < minX) {
          minX = lane.x;
        }
        // height
        if (lane.y + lane.height > maxY) {
          maxY = lane.y + lane.height;
        }
        if (lane.y < minY) {
          minY = lane.y;
        }
      }
    }

    var diagramCanvas = new ProcessDiagramCanvas();
    if (diagramCanvas) {

      // create div in diagramHolder
      var diagramHolder = document.getElementById(this.options.diagramHolderId);
      if (!diagramHolder)
        throw {msg: "Diagram holder not found", error: "diagramHolderNotFound"};
      var div = document.createElement("DIV");
      div.id = processDefinitionDiagramLayout.processDefinition.id;
      div.className = "diagram";
      diagramHolder.appendChild(div);

      diagramCanvas.init(maxX + 20, maxY + 20, processDefinitionDiagramLayout.processDefinition.id);
      this.processDiagrams[processDefinitionDiagramLayout.processDefinition.id] = {
        processDefinitionDiagramLayout: processDefinitionDiagramLayout,
        diagramCanvas: diagramCanvas
      };
    }
    return this.getProcessDiagram(processDefinitionDiagramLayout.processDefinition.id);
    //return new DefaultProcessDiagramCanvas(maxX + 10, maxY + 10, minX, minY);
  },

  drawActivity: function (processDiagramCanvas, activity, highLightedActivities) {
    var type = activity.getProperty("type");
    var drawInstruction = this.activityDrawInstructions[type];
    if (drawInstruction != null) {
      drawInstruction.apply({processDiagramCanvas: processDiagramCanvas, activity: activity});
    } else {
      //console.error("no drawInstruction for " + type + ": ", activity);
    }

    // Actually draw the markers
    if (activity.getProperty("multiInstance") != undefined || activity.getProperty("collapsed") != undefined) {
      //console.log(activity.getProperty("name"), activity.properties);
      var multiInstanceSequential = (activity.getProperty("multiInstance") == "sequential");
      var multiInstanceParallel = (activity.getProperty("multiInstance") == "parrallel");
      var collapsed = activity.getProperty("collapsed");
      processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(),
        multiInstanceSequential, multiInstanceParallel, collapsed);
    }
    /*
    processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(), multiInstanceSequential,
              multiInstanceParallel, collapsed);
    */

    // TODO: Draw highlighted activities if they are present

  },

  setHighLights: function (highLights) {
    if (highLights.processDefinitionId == undefined) {
      //console.error("Process instance " + highLights.processInstanceId + " doesn't exist");
      return;
    }

    var processDiagram = this.getProcessDiagram(highLights.processDefinitionId);
    if (processDiagram == undefined) {
      //console.error("Process diagram " + highLights.processDefinitionId + " not found");
      return;
    }

    var processDiagramCanvas = processDiagram.diagramCanvas;

    // TODO: remove highLightes from all activities before set new highLight
    for (var i in highLights.activities) {
      var activityId = highLights.activities[i];
      processDiagramCanvas.highLightActivity(activityId);
    }

    // TODO: remove highLightes from all flows before set new highLight
    for (var i in highLights.flows) {
      var flowId = highLights.flows[i];
      var object = processDiagramCanvas.g.getById(flowId);
      var flow = object.data("contextObject");
      flow.isHighLighted = true;
      processDiagramCanvas.highLightFlow(flowId);
    }
  },

  drawHighLights: function (processInstanceId) {
    // Load highLights for the processInstanceId
    /*
    var url = Lang.sub(this.options.processInstanceHighLightsUrl, {processInstanceId: processInstanceId});
    $.ajax({
      url: url,
      type: 'GET',
      dataType: 'json',
      cache: false,
      async: true,
    }).done(function(data) {
      var highLights = data;
      if (!highLights) {
        console.log("highLights not found");
        return;
      }

      console.log("highLights[" + highLights.processDefinitionId + "][" + processInstanceId + "]: ", highLights);

      ProcessDiagramGenerator.setHighLights(highLights);
    }).fail(function(jqXHR, textStatus){
      console.log('Get HighLights['+processDefinitionId+'] failure: ', textStatus, jqXHR);
    });
    */
    ActivitiRest.getHighLights(processInstanceId, this._drawHighLights);
  },
  _drawHighLights: function () {
    var highLights = this.highLights;
    ProcessDiagramGenerator.setHighLights(highLights);
  },

  // Load processDefinition

  drawDiagram: function (processDefinitionId) {
    // Hide all diagrams
    var diagrams = $("#" + this.options.diagramHolderId + " div.diagram");
    diagrams.addClass("hidden");


    // If processDefinitionId doesn't contain ":" then it's a "processDefinitionKey", not an id.
    // Get process definition by key
    if (processDefinitionId.indexOf(":") < 0) {
      ActivitiRest.getProcessDefinitionByKey(processDefinitionId, this._drawDiagram);
    } else {
      this._drawDiagram.apply({processDefinitionId: processDefinitionId});
    }
  },
  _drawDiagram: function () {
    var processDefinitionId = this.processDefinitionId;

    ProcessDiagramGenerator.addBreadCrumbsItem(processDefinitionId);


    // Check if processDefinition is already loaded and rendered


    var processDiagram = ProcessDiagramGenerator.getProcessDiagram(processDefinitionId);

    if (processDiagram != undefined && processDiagram != null) {
      //console.log("Process diagram " + processDefinitionId + " is already loaded");
      //return;

      var diagram = document.getElementById(processDefinitionId);
      $(diagram).removeClass("hidden");

      // Regenerate image
      var processDefinitionDiagramLayout = processDiagram.processDefinitionDiagramLayout;
      ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);

      return;
    }

    //console.time('loadDiagram');

    // Load processDefinition

    ActivitiRest.getProcessDefinition(processDefinitionId, ProcessDiagramGenerator._generateDiagram);
  },
  _generateDiagram: function () {
    var processDefinitionDiagramLayout = this.processDefinitionDiagramLayout;

    //console.log("process-definition-diagram-layout["+processDefinitionDiagramLayout.processDefinition.id+"]: ", processDefinitionDiagramLayout);

    //console.timeEnd('loadDiagram');
    //console.time('generateDiagram');

    pb1.set('value', 0);
    ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
  },

  getProcessDefinitionByKey: function (processDefinitionKey) {
    var url = Lang.sub(this.options.processDefinitionByKeyUrl, {processDefinitionKey: processDefinitionKey});

    var processDefinition;
    $.ajax({
      url: url,
      type: 'POST',
      dataType: 'json',
      cache: false,
      async: false
    }).done(function (data) {
      //console.log("ajax returned data");
      //console.log("ajax returned data:", data);
      processDefinition = data;
      if (!processDefinition) {
        //console.error("Process definition '" + processDefinitionKey + "' not found");
      }
    }).fail(function (jqXHR, textStatus) {
      //console.error('Get diagram layout['+processDefinitionKey+'] failure: ', textStatus, jqXHR);
    });

    if (processDefinition) {
      //console.log("Get process definition by key '" + processDefinitionKey + "': ", processDefinition.id);
      return processDefinition;
    } else {
      return null;
    }
  },

  addBreadCrumbsItem: function (processDefinitionId) {
    var TPL_UL_CONTAINER = '<ul></ul>',
      TPL_LI_CONTAINER = '<li id="{id}", processDefinitionId="{processDefinitionId}"><span>{name}</span></li>';

    if (!this.diagramBreadCrumbs)
      this.diagramBreadCrumbs = $("#" + this.options.diagramBreadCrumbsId);
    if (!this.diagramBreadCrumbs) return;


    var ul = this.diagramBreadCrumbs.find("ul");
    //console.log("ul: ", ul);
    if (ul.size() == 0) {
      ul = $(TPL_UL_CONTAINER);
      this.diagramBreadCrumbs.append(ul);

    }
    var liListOld = ul.find("li");
    //console.warn("liListOld", liListOld);

    // TODO: if there is any items after current then remove that before adding new item (m.b. it is a duplicate)
    var currentBreadCrumbsItemId = this.currentBreadCrumbsItemId;
    found = false;
    liListOld.each(
      function (index, item) {
        //console.warn("item:", $(this));
        if (!found && currentBreadCrumbsItemId == $(this).attr("id")) {
          found = true;
          return;
        }
        if (found) {
          //console.warn("remove ", $(this).attr("id"));
          $(this).remove();
        }
      }
    );

    var liListNew = ul.find("li");

    //console.log("liListNew size: ", liListNew.size());
    var values = {
      id: 'breadCrumbsItem_' + liListNew.size(),
      processDefinitionId: processDefinitionId,
      name: processDefinitionId
    };


    var tpl = Lang.sub(TPL_LI_CONTAINER, values);
    //console.log("tpl: ", tpl);
    ul.append(tpl);

    var li = ul.find("#" + values.id);
    //console.warn("li:", li);
    $('#' + values.id).on('click', this._breadCrumbsItemClick);

    ul.find("li").removeClass("selected");
    li.attr("num", liListNew.size());
    li.addClass("selected");
    this.currentBreadCrumbsItemId = li.attr("id");
  },
  _breadCrumbsItemClick: function () {
    var li = $(this),
      id = li.attr("id"),
      processDefinitionId = li.attr("processDefinitionId");
    //console.warn("_breadCrumbsItemClick: ", id, ", processDefinitionId: ", processDefinitionId);

    var ul = ProcessDiagramGenerator.diagramBreadCrumbs.one("ul");
    ul.find("li").removeClass("selected");
    li.addClass("selected");
    ProcessDiagramGenerator.currentBreadCrumbsItemId = li.attr("id");

    // Hide all diagrams
    var diagrams = $("#" + ProcessDiagramGenerator.options.diagramHolderId + " div.diagram");
    diagrams.addClass("hidden");

    var processDiagram = ProcessDiagramGenerator.getProcessDiagram(processDefinitionId);

    var diagram = document.getElementById(processDefinitionId);
    if (!diagram) return;
    $(diagram).removeClass("hidden");

    // Regenerate image
    var processDefinitionDiagramLayout = processDiagram.processDefinitionDiagramLayout;
    ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
  },

  showFlowInfo: function (flow) {
    var diagramInfo = $("#" + this.options.diagramInfoId);
    if (!diagramInfo) return;

    var values = {
      flow: flow.flow,
      isDefault: (flow.isDefault) ? "true" : "",
      isConditional: (flow.isConditional) ? "true" : "",
      isHighLighted: (flow.isHighLighted) ? "true" : "",
      sourceActivityId: flow.sourceActivityId,
      destinationActivityId: flow.destinationActivityId
    };
    var TPL_FLOW_INFO = '<div>{flow}</div>'
      + '<div><b>sourceActivityId</b>: {sourceActivityId}</div>'
      + '<div><b>destinationActivityId</b>: {destinationActivityId}</div>'
      + '<div><b>isDefault</b>: {isDefault}</div>'
      + '<div><b>isConditional</b>: {isConditional}</div>'
      + '<div><b>isHighLighted</b>: {isHighLighted}</div>';
    var tpl = Lang.sub(TPL_FLOW_INFO, values);
    //console.log("info: ", tpl);
    diagramInfo.html(tpl);
  },

  showActivityInfo: function (activity) {
    var diagramInfo = $("#" + this.options.diagramInfoId);
    if (!diagramInfo) return;

    var values = {
      activityId: activity.getId(),
      name: activity.getProperty("name"),
      type: activity.getProperty("type")
    };
    var TPL_ACTIVITY_INFO = ''
      + '<div><b>activityId</b>: {activityId}</div>'
      + '<div><b>name</b>: {name}</div>'
      + '<div><b>type</b>: {type}</div>';
    var TPL_CALLACTIVITY_INFO = ''
      + '<div><b>collapsed</b>: {collapsed}</div>'
      + '<div><b>processDefinitonKey</b>: {processDefinitonKey}</div>';

    var template = TPL_ACTIVITY_INFO;
    if (activity.getProperty("type") == "callActivity") {
      values.collapsed = activity.getProperty("collapsed");
      values.processDefinitonKey = activity.getProperty("processDefinitonKey");
      template += TPL_CALLACTIVITY_INFO;
    } else if (activity.getProperty("type") == "callActivity") {

    }

    var tpl = Lang.sub(template, values);
    //console.log("info: ", tpl);
    diagramInfo.html(tpl);
  },

  hideInfo: function () {
    var diagramInfo = $("#" + this.options.diagramInfoId);
    if (!diagramInfo) return;
    diagramInfo.html("");
  },

  vvoid: function () {
  }
};

var Lang = {
  SUBREGEX: /\{\s*([^\|\}]+?)\s*(?:\|([^\}]*))?\s*\}/g,
  UNDEFINED: 'undefined',
  isUndefined: function (o) {
    return typeof o === Lang.UNDEFINED;
  },
  sub: function (s, o) {
    return ((s.replace) ? s.replace(Lang.SUBREGEX, function (match, key) {
      return (!Lang.isUndefined(o[key])) ? o[key] : match;
    }) : s);
  }
};

if (Lang.isUndefined(console)) {
  console = {
    log: function () {
    }, warn: function () {
    }, error: function () {
    }
  };
}
ProcessDiagramGenerator.init();
